#ifndef SDK_H
#define SDK_H

#define ProgramName "Otter Music Player"
#define __ERRORSTRINGIFY__(x) #x
#define __ERRORTOSTRING__(x) __ERRORSTRINGIFY__(x)
#define __ERRORDATA__ __FILE__ ":" __ERRORTOSTRING__(__LINE__)

#include <string>
#include <iostream>
#include <fstream>
#include <map>
#include <vector>

#include <stdio.h>
#include <unistd.h>
#include <cstring>
#include <pthread.h>
#include <exception>

enum EventType { LDoubleClick, RDoubleClick, LBtnDown, LBtnUp, MBtnDown, MBtnUp, RBtnDown, RBtnUp, Wheel, Key, ChangePlaybackState, NewTrack, ChangeMetadata, Custom, MouseEnter, MouseLeave, Reposition };
enum PlaybackState { Paused, Stopped, Playing, DevNotReady, ResourceBusy };
enum ElementPrototype { Button };
enum InfoTypeChange { InfoFrequency, InfoChannels, InfoBitWidth, InfoSampleRate };

struct Position {
	int x_px;
	int y_px;
	int Width_px;
	int Height_px;
	double x_pc;
	double y_pc;
	double Width_pc;
	double Height_pc;
	bool UsePC;
};

struct Event {
	EventType type;
	Position pos;
	struct LDATA {
		int EventNumber;
		long dataH;
		long dataL;
	};
};

class Error : public std::exception {
private:
	std::string Type;
	std::string Details;
	std::string Filename;
	char Number;
	bool SuccessB;
public:
	bool Success() {
		return SuccessB;
	}
	std::string ReturnType() {
		return Type;
	}
	std::string ReturnDetails() {
		return Details;
	}
	char ReturnErrorNo() {
		return Number;
	}
	Error PrintToConsole();
	template <typename T1, typename T2, typename T3>
	Error(T1 Filename_, T2 Type_, T3 Details_, char Number_ = 0xff) {
		Type = Type_;
		Details = Details_;
		Filename = Filename_;
		Number = Number_;
		SuccessB = false;
	}
	Error() {
		SuccessB = true;
	}
	virtual const char* what() const throw() {
		return ("<" + Filename + ">: " + Type + ": " + Details).c_str();
	}
};

class Config
{
private:
	std::string BConfLocation;
	std::map<const char*, int> IntConfig;
	std::map<const char*, const char*> StringConfig;
public:
	Config() {
		std::ifstream ifs_(BConfLocation.c_str());
		if (ifs_) {
			std::string Output_;
			std::string OutputTmp_;
			std::string Name_;
			std::string Type_;
			unsigned int Pos_, Pos2_;
			while (!ifs_.eof()) {
				getline(ifs_, Output_);
				if (Output_[0] != 59) { //59 = ";"
					Pos_ = Output_.find(" ");
					Pos2_ = Output_.find("=");
					if (Output_.find(" =") == Pos2_-1)
						Name_ = Name_.substr(0, Name_.find(" ", Pos_+1));
					if (Output_.substr(0, Pos_) == std::string("int"))
						IntConfig[Name_.c_str()] = std::stoi(Output_.substr(Pos2_+1, Output_.length()-Pos2_));
					else if (Output_.substr(0, Pos_) == std::string("str"))
						StringConfig[Name_.c_str()] = Output_.substr(Pos2_+1, Output_.length()-Pos2_).c_str();
				}
			}
			ifs_.close();
		} else {
			throw Error("SDK.h", "File read error", std::string("Error while reading config file: ") + BConfLocation);
		}
	}
	Error Save() {
		std::ofstream ofs_(BConfLocation.c_str());
		if (ofs_) {
			ofs_ << "; " << ProgramName << " configuration file." << std::endl << "; Note that spaces after the equality sign will be interpreted as part of the variable's value." << std::endl;
			for (std::map<const char*, int>::iterator it_ = IntConfig.begin(); it_ != IntConfig.end(); ++it_) {
				ofs_ << "int " << it_->first;
				ofs_ << "=" << it_->second << std::endl;
			}
			for (std::map<const char*, const char*>::iterator it_ = StringConfig.begin(); it_ != StringConfig.end(); ++it_) {
				ofs_ << "str " << it_->first;
				ofs_ << "=" << it_->second << std::endl;
			}
			ofs_.close();
		} else {
			return Error("SDK.h", "File write error", std::string("Error while writing config file: ") + BConfLocation);
		}
	}
	const char* GetStringValue(char* Name) {
		return StringConfig[Name];
	}
	int GetIntegerValue(char* Name) {
		return IntConfig[Name];
	}
	void SetValue(char* Name, char* Value) {
		StringConfig[Name] = Value;
	}
	void SetValue(char* Name, int Value) {
		IntConfig[Name] = Value;
	}
};

struct FileInfo {
	int SampleRate;
	int Channels;
	int BitWidth;
};

class PCMPlayer;
class MusicCodec;

class _OtterSDK {
private:
	pthread_mutex_t* PCMCodecMutex;
	pthread_cond_t*  PCMCodecCond;

	char* PCMBuffer;
	bool PCMBufferInUse;
	bool PCMRunning;
	bool CodecRunning;
	int PCMBufferSize;

	int SamplingFrequency;
	int Channels;
	int BitWidth;
	int SampleRate;

	PCMPlayer* PCM;
	MusicCodec* Codec;

	pthread_t CodecThread;
	pthread_t PCMThread;
public:
	void ChangeTrackInfo(InfoTypeChange TypeChange, int Value) {
		if (TypeChange == InfoFrequency)
				SamplingFrequency = Value;
		if (TypeChange == InfoChannels)
				Channels = Value;
		if (TypeChange == InfoBitWidth)
				BitWidth = Value;
		if (TypeChange == InfoSampleRate)
				SampleRate = Value;
	}
	int GetSamplingFrequency() {
		return SamplingFrequency;
	}
	int GetChannels() {
		return Channels;
	}
	MusicCodec* GetCurrentCodec() {
		if (Codec)
			return Codec;
		else
			return NULL;
	}
	PCMPlayer*  GetCurrentPlayer() {
		if (PCM)
			return PCM;
		else
			return NULL;
	}
	void ReportCodecConstruction(MusicCodec* c) {
        Codec = c;
    }
    void ReportPCMConstruction(PCMPlayer* p) {
        PCM = p;
    }
	Error UnloadCodec();
	Error UnloadPlayer();
	Error LoadCodec(std::string CodecName);
	Error LoadPlayer(std::string CodecName);
	Error Play(std::string FileName);
	int GetBitScaling() {
		return 24;
	}
	int GetFileBufferSize() {
		return 2048;
	}
	int GetPCMBufferSize() {
		return PCMBufferSize;
	}
	int SuggestPCMBufferSize(int Size) {
		if (!PCMBufferInUse) {
			PCMBufferSize = Size;
			if (PCMBuffer)
				delete[] PCMBuffer;
			PCMBuffer = new char[PCMBufferSize];
		}
		return PCMBufferSize;
	}
	char* GetPCMBuffer() {
		if (!PCMBuffer)
			PCMBuffer = new char[PCMBufferSize];
		if (!PCMBufferInUse)
			PCMBufferInUse = true;
		return PCMBuffer;
	}
	bool CodecPCMSignalAndWait() {
		pthread_mutex_lock(PCMCodecMutex);
			pthread_cond_signal(PCMCodecCond);
			pthread_cond_wait(PCMCodecCond, PCMCodecMutex);
		pthread_mutex_unlock(PCMCodecMutex);
		return true;
	}
	void ReportCodecState(bool state) {
        pthread_mutex_lock(PCMCodecMutex);
            CodecRunning = state;
        pthread_mutex_unlock(PCMCodecMutex);
	}
	void ReportPCMState(bool state) {
        pthread_mutex_lock(PCMCodecMutex);
            PCMRunning = state;
        pthread_mutex_unlock(PCMCodecMutex);
	}
	Error DropPCMDevice();
	_OtterSDK(pthread_mutex_t* mutex, pthread_cond_t* cond) {
        PCMCodecMutex = mutex;
        PCMCodecCond  = cond;
	}
};

extern _OtterSDK* OtterSDK;

#endif // SDK_H
